package org.breathe.server.impl;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.*;
import lombok.extern.slf4j.Slf4j;
import org.breathe.aof.AofManager;
import org.breathe.rdb.RdbManager;
import org.breathe.server.RedisServer;
import org.breathe.server.core.RedisCore;
import org.breathe.server.core.impl.RedisCoreImpl;
import org.breathe.server.handler.RespCommandHandler;
import org.breathe.server.handler.RespDecoder;
import org.breathe.server.handler.RespEncoder;

import java.io.FileNotFoundException;

/**
 * redis server impl
 * @author: breathe
 * @createTime: 2025-05-12
 */
@Slf4j
public class MiniRedisServer implements RedisServer {
    private static final int DEFAULT_DB_COUNT = 16;
    private String host;
    private int port;
    private EventLoopGroup bossGroup;
    private EventExecutorGroup commandExecutor;
    public RespCommandHandler respCommandHandler;
    private EventLoopGroup workerGroup;
    private Channel serverChannel;
    private RedisCore redisCore;
    private static final boolean ENABLED_AOF = false;
    private static final boolean ENABLED_RDB = true;
    private AofManager aofManager;
    private RdbManager rdbManager;
    public MiniRedisServer(String host, int port) throws Exception {
        this.host = host;
        this.port = port;
        this.bossGroup = new NioEventLoopGroup(1);
        this.workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2);
        this.commandExecutor = new DefaultEventExecutorGroup(1, new DefaultThreadFactory("redis-cmd"));
        this.redisCore = new RedisCoreImpl(16, this);
        this.aofManager = null;
        if (ENABLED_AOF) {
            this.aofManager = new AofManager("breathe-redis.aof", redisCore);
            aofManager.load();
            //loader time
            Thread.sleep(500);
        }
        if (ENABLED_RDB) {
            this.rdbManager = new RdbManager(redisCore);
            boolean success = rdbManager.loadRdb();
            if (!success) {
                log.warn("RDB file load fail, may file not exists or file format error");
            }
            Thread.sleep(500);
        }
        this.respCommandHandler = new RespCommandHandler(redisCore, aofManager);
    }
    @Override
    public void start() {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel socketChannel) throws Exception{
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new RespDecoder());
                        pipeline.addLast(respCommandHandler);
                        pipeline.addLast(new RespEncoder());
                    }
                });
        try {
            serverChannel = serverBootstrap.bind(host, port)
                    .sync()
                    .channel();
            log.info("Redis server started at {} : {}", host, port);
        } catch (InterruptedException ex) {
            log.error("Redis server start error", ex);
            stop();
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public void stop() {
        try {
            if (serverChannel != null) {
                serverChannel.close().sync();
            }
            if (workerGroup != null) {
                workerGroup.shutdownGracefully().sync();
            }
            if (bossGroup != null) {
                bossGroup.shutdownGracefully().sync();
            }
            if (aofManager != null) {
                aofManager.close();
            }
        } catch (InterruptedException ex) {
            log.error("Redis server stop error", ex);
            Thread.currentThread().interrupt();
        } catch(Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public RdbManager getRdbManager() {
        return rdbManager;
    }
}
